#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import argparse
import json
from typing import List, Dict, Any, Optional

from docx import Document
from docx.enum.table import WD_TABLE_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.shared import Pt, Inches
from docx.table import _Cell, Table
from docx.oxml import OxmlElement
from docx.oxml.ns import qn
from docx.enum.section import WD_ORIENT
from docx.shared import Cm
# ---------------- 基础工具：文本、样式、底纹、表头重复 ----------------
def norm_text(s: Any) -> str:
    return "" if s is None else str(s).replace("\xa0", " ").strip()

def join_inline(items: List[str]) -> str:
    if not items:
        return ""
    items = [norm_text(x) for x in items if norm_text(x)]
    return "，".join(items)   # 合并为一行，交给 Word 自动换行

def verdict_symbol(v: Optional[bool]) -> str:
    if v is True: return "√"
    if v is False: return "×"
    return ""

def set_cell_text(cell: _Cell, text: str, *, bold: bool=False, align: str="left",
                  font_size: float=10.5, font_name: str="等线"):
    """统一设置单元格段落与字体，避免排版混乱"""
    cell.text = ""
    p = cell.paragraphs[0]
    run = p.add_run(norm_text(text))
    run.bold = bold
    p.paragraph_format.space_after = 0
    p.paragraph_format.space_before = 0
    p.alignment = {"left": WD_ALIGN_PARAGRAPH.LEFT,
                   "center": WD_ALIGN_PARAGRAPH.CENTER,
                   "right": WD_ALIGN_PARAGRAPH.RIGHT}.get(align, WD_ALIGN_PARAGRAPH.LEFT)
    for r in p.runs:
        r.font.name = font_name
        r.font.size = Pt(font_size)
        # 设置东亚字体，避免中文回退成西文字体
        r._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)

def shade_cell(cell: _Cell, fill: str="D9D9D9"):
    """给单元格加底纹（用于表头两行与列标题）"""
    tc_pr = cell._tc.get_or_add_tcPr()
    shd = OxmlElement("w:shd")
    shd.set(qn("w:val"), "clear")
    shd.set(qn("w:color"), "auto")
    shd.set(qn("w:fill"), fill)
    tc_pr.append(shd)

def set_repeat_table_header(row):
    """跨页重复显示该行（用于步骤表表头两行 + 列标题）"""
    tr = row._tr
    trPr = tr.get_or_add_trPr()
    tblHeader = OxmlElement('w:tblHeader')
    tblHeader.set(qn('w:val'), "true")
    trPr.append(tblHeader)


# ---------------- 生成器 ----------------
class CaseDocxBuilder:
    """将 JSON 用例列表生成为 Word 文档（用例抬头表 + 步骤表 + 表尾）"""
    def __init__(self, cases: List[Dict[str, Any]]):
        self.cases = cases
        # 步骤表列宽（与模板比例相近）
        self.step_col_widths = [Inches(1.0), Inches(4.3), Inches(3.0), Inches(1.3), Inches(1.0)]

    def build(self, output_path: str):
        doc = Document()
        for idx, case in enumerate(self.cases):
            if idx > 0:
                doc.add_page_break()
            self._add_case(doc, case)
        #获取本文档中的所有章节
        sections = doc.sections
        #将该章节中的纸张方向设置为横向
        for section in sections:
            #需要同时设置width,height才能成功
            new_width, new_height = Cm(29.7),Cm(21)
            section.orientation = WD_ORIENT.LANDSCAPE
            section.page_width = new_width 
            section.page_height = new_height
        doc.save(output_path)

    def _add_case(self, doc: Document, case: Dict[str, Any]):
        self._add_header_table(doc, case)   # 用例抬头表
        doc.add_paragraph("")               # 分隔
        self._add_steps_table(doc, case)    # 步骤表（两行信息头 + 列标题）
        self._add_steps_footer(doc)         # 步骤表尾（备注 + 审核）

    # ---------- 用例抬头表：2行×2列，位置与顺序严格按模板 ----------
    def _add_header_table(self, doc: Document, case: Dict[str, Any]):
        table = doc.add_table(rows=2, cols=2)
        table.alignment = WD_TABLE_ALIGNMENT.CENTER
        table.style = "Table Grid"

        # 宽左窄右：左侧用于“目的/测试覆盖”，右侧用于“执行次数/前提条件”
        left_w, right_w = Inches(5.3), Inches(5.3)
        for r in table.rows:
            try:
                r.cells[0].width = left_w
                r.cells[1].width = right_w
            except Exception:
                pass

        test_purpose = norm_text(case.get("test_purpose"))
        test_coverage = join_inline(case.get("test_coverage") or [])
        precond = norm_text(case.get("preconditions"))

        # 第一行：左=目的（灰底），右=执行次数（灰底）
        shade_cell(table.cell(0, 0), "D9D9D9")
        set_cell_text(table.cell(0, 0), f"测试目的：{test_purpose}", align="left")
        shade_cell(table.cell(0, 1), "D9D9D9")
        set_cell_text(table.cell(0, 1), "执行次数：□第一次执行    □其他：____________", align="left")

        # 第二行：左=测试覆盖（灰底），右=前提条件（无灰底）
        shade_cell(table.cell(1, 0), "D9D9D9")
        set_cell_text(table.cell(1, 0), f"测试覆盖：{test_coverage}", align="left")
        set_cell_text(table.cell(1, 1), f"前提条件：{precond}", align="left")

    # ---------- 步骤表：信息头两行 + 列标题（均跨页重复） ----------
    def _add_steps_table(self, doc: Document, case: Dict[str, Any]):
        steps = case.get("test_steps") or []
        test_phase = norm_text(case.get("test_phase"))
        test_case_id = norm_text(case.get("test_case_id"))
        executor = norm_text(case.get("executor_signature"))

        cols = ["步骤", "测试步骤", "预期结果", "实际结果/附件", "结论"]
        # 预建 3 行：信息头1、信息头2、列标题
        table = doc.add_table(rows=3, cols=len(cols))
        table.alignment = WD_TABLE_ALIGNMENT.CENTER
        table.style = "Table Grid"

        # 列宽
        for i, w in enumerate(self.step_col_widths):
            try:
                table.columns[i].width = w
            except Exception:
                pass

        # 信息头第1行：左合并0..2=测试阶段；右合并3..4=测试用例
        info1 = table.rows[0]
        left1 = info1.cells[0].merge(info1.cells[1])
        right1 = info1.cells[2].merge(info1.cells[3]).merge(info1.cells[4])
        shade_cell(left1, "D9D9D9"); shade_cell(right1, "D9D9D9")
        set_cell_text(left1,  f"测试阶段：{test_phase}", align="left")
        set_cell_text(right1, f"测试用例：{test_case_id}", align="left")
        set_repeat_table_header(table.rows[0])

        # 信息头第2行：左合并0..2=测试人印刷体姓名；右合并3..4=测试人签字及日期
        info2 = table.rows[1]
        left2 = info2.cells[0]
        right2 = info2.cells[3].merge(info2.cells[4])
        shade_cell(left2, "D9D9D9"); shade_cell(info2.cells[2], "D9D9D9")
        set_cell_text(left2,  "测试人姓名", align="left")
        set_cell_text(info2.cells[1],  f"{executor}", align="left")
        set_cell_text(info2.cells[2], "测试人签字及日期", align="left")
        set_cell_text(right2, " ", align="left")
        set_repeat_table_header(table.rows[1])

        # 列标题（第三行）
        hdr = table.rows[2].cells
        for i, name in enumerate(cols):
            shade_cell(hdr[i], "D9D9D9")
            set_cell_text(hdr[i], name, bold=True, align="center", font_size=11)
        set_repeat_table_header(table.rows[2])

        # 数据行（按顺序填充）
        for st in steps:
            row = table.add_row().cells
            set_cell_text(row[0], str(st.get("step_number") or ""), align="center")
            set_cell_text(row[1], norm_text(st.get("step_description") or ""))
            set_cell_text(row[2], norm_text(st.get("expected_result") or ""))
            # 实际结果：布尔→通过/失败；文本/空→原样
            ar = st.get("actual_result")
            if isinstance(ar, bool):
                set_cell_text(row[3], "通过" if ar else "失败")
            else:
                set_cell_text(row[3], norm_text(ar) if ar is not None else "")
            # 结论符号：√ / × / 空
            set_cell_text(row[4], verdict_symbol(st.get("is_passed")), align="center", font_size=12)

    # ---------- 步骤表尾（备注 + 数据审核，按模板：无灰底、不加粗） ----------
    def _add_steps_footer(self, doc: Document):
        table: Table = doc.add_table(rows=2, cols=5)
        table.alignment = WD_TABLE_ALIGNMENT.CENTER
        table.style = "Table Grid"

        for i, w in enumerate(self.step_col_widths):
            try:
                table.columns[i].width = w
            except Exception:
                pass

        # 备注：标签 + 合并值单元格
        set_cell_text(table.cell(0, 0), "备注：", bold=False, align="left")
        table.cell(0, 1).merge(table.cell(0, 4))
        set_cell_text(table.cell(0, 1), "", align="left")

        # 数据审核：左=印刷体姓名，右=签字及日期；均不灰、不加粗
        set_cell_text(table.cell(1, 0), "数据审核人印刷体姓名", bold=False, align="left")
        set_cell_text(table.cell(1, 1), "", align="left")
        set_cell_text(table.cell(1, 2), "数据审核人签字及日期", bold=False, align="left")
        table.cell(1, 3).merge(table.cell(1, 4))
        set_cell_text(table.cell(1, 3), "", align="left")


# ---------------- 加载 JSON ----------------
def load_cases(json_path: str) -> List[Dict[str, Any]]:
    with open(json_path, "r", encoding="utf-8") as f:
        data = json.load(f)
    if isinstance(data, dict):
        return [data]
    if isinstance(data, list):
        return data
    raise ValueError("Input JSON must be dict or list of dict.")


def main():
    parser = argparse.ArgumentParser(description="Build Word (.docx) testcases (template-accurate, two-row headers)")
    parser.add_argument("-i", "--input", required=True, help="input JSON path")
    parser.add_argument("-o", "--output", required=True, help="output DOCX path")
    args = parser.parse_args()

    cases = load_cases(args.input)
    CaseDocxBuilder(cases).build(args.output)
    print(f"Built {len(cases)} case(s) → {args.output}")


if __name__ == "__main__":
    main()